package com.lam.schedule.common.scheduler.factory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.Trigger.TriggerState;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.NumberUtil;
import com.lam.schedule.common.constant.ScheduleConstant;
import com.lam.schedule.common.scheduler.model.QuartzJobDisallowConcurrentExecution;
import com.lam.schedule.common.scheduler.model.QuartzJobExecution;
import com.lam.schedule.common.scheduler.model.ScheduleProperties;
import com.lam.schedule.common.scheduler.util.InvokeUtils;
import com.lam.schedule.common.scheduler.util.TaskUtils;
import com.lam.schedule.entity.ScheduleJob;
import com.lam.schedule.param.ScheduleJobParam;
import com.lam.schedule.service.ScheduleJobService;

/**
 * @author lam
 *
 */
@Service
public class ScheduleManage {
	
	private final Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	private SchedulerFactoryBean schedulerFactoryBean;
	@Autowired
	private ScheduleJobService scheduleJobService;
	@Autowired
	private ScheduleProperties scheduleProperties;
	
	public ScheduleProperties getScheduleProp() {
		return scheduleProperties;
	}
	
	public boolean isScheduleClustered() {
		return scheduleProperties.isClustered();
	}
	
	public Scheduler getScheduler() {
		return schedulerFactoryBean.getScheduler();
	}
	
	public void init(){
		logger.info("初始化定时任务列表准备...");
		Scheduler scheduler = getScheduler();
		//初始化定时任务列表
		try {
			scheduler.clear();
		} catch (SchedulerException e) {
			logger.error("初始化清空任务失败", e);
		}
		//从数据库加载定时任务
		ScheduleJobParam scheduleJobParam = new ScheduleJobParam();
		scheduleJobParam.setStatus(ScheduleConstant.JOB_STATUS_YES);
		List<ScheduleJob> jobList = scheduleJobService.selectList(scheduleJobParam);
		logger.info("初始化定时任务列表，共{}个任务!", jobList.size());
		int n = 0;
		String res = null;
		for (ScheduleJob job : jobList) {
			res = InvokeUtils.checkJob(job);
			if(res != null){
				logger.error("添加定时任务失败:{}, job:{}", res, job);
				continue;
			}
			try {
				addJob(job);
				n++;
			} catch (SchedulerException e) {
				logger.error("任务添加到执行列表失败, job:{}", job);
				logger.error(e.getMessage(), e);
			}
		}
		logger.info("初始化定时任务列表，共{}个任务初始化成功!", n);
	}


	/**
	 * 添加任务
	 * 
	 * @param job
	 * @throws SchedulerException
	 */
	public void addJob(ScheduleJob job) throws SchedulerException {
		if (job == null || !ScheduleConstant.JOB_STATUS_YES.equals(job.getStatus())) {
			return;
		}

		Scheduler scheduler = getScheduler();
		TriggerKey triggerKey = TaskUtils.getTriggerKey(job);

		CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

		// 不存在，创建一个
		if (trigger == null) {
			logger.info("add job, job:{}", job);
			Class<? extends Job> clazz = NumberUtil.eq(ScheduleConstant.CONCURRENT_TYPE_Y, job.getConcurrentType()) ? 
					QuartzJobExecution.class : QuartzJobDisallowConcurrentExecution.class;
			JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(TaskUtils.getJobKey(job)).build();
			jobDetail.getJobDataMap().put(ScheduleConstant.JOB_DATA_KEY, job);
			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
			trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
					.withSchedule(scheduleBuilder).build();

			scheduler.scheduleJob(jobDetail, trigger);
		} else {
			logger.info("update job, job:{}", job);
			// Trigger已存在，那么更新相应的定时设置
			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()).withMisfireHandlingInstructionDoNothing();

			// 按新的cronExpression表达式重新构建trigger
			trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

			// 按新的trigger重新设置job执行
			scheduler.rescheduleJob(triggerKey, trigger);
		}
	}
	
	/**
	 * 获取所有计划中的任务列表,单机版适用，集群时在非当前节点执行的任务状态不准确
	 * @return
	 * @throws SchedulerException
	 */
	public List<ScheduleJob> getAllJob() throws SchedulerException {
		Scheduler scheduler = getScheduler();
		Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
		List<ScheduleJob> jobList = new ArrayList<>();
		Map<String, ScheduleJob> jobMap = new HashMap<>();
		ScheduleJob scheduleJob;
		for (JobKey jobKey : jobKeys) {
			scheduleJob = jobMap.get(jobKey.getName());
			if(scheduleJob == null) {
				scheduleJob = (ScheduleJob)scheduler.getJobDetail(jobKey).getJobDataMap().get(ScheduleConstant.JOB_DATA_KEY);
				jobMap.put(scheduleJob.getJobKey(), scheduleJob);
				jobList.add(scheduleJob);
			}
			List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
			if(CollectionUtil.isNotEmpty(triggers)) {
				scheduleJob.setScheduleStatus("");
			}
			for (Trigger trigger : triggers) {
				scheduleJob.setScheduleStatusAdd(scheduler.getTriggerState(trigger.getKey()).name());
			}
		}
		return jobList;
	}
	
	public List<? extends Trigger> getTriggersOfJob(ScheduleJob scheduleJob) throws SchedulerException{
		return getScheduler().getTriggersOfJob(TaskUtils.getJobKey(scheduleJob));
		
	}

	/**
	 * 所有正在运行的job,单机版适用，集群时在非当前节点执行的任务状态会显示为现成阻塞，而非正在执行
	 * 
	 * @return
	 * @throws SchedulerException
	 */
	public List<ScheduleJob> getRunningJob() throws SchedulerException {
		Scheduler scheduler = getScheduler();
		List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
		Map<String, ScheduleJob> jobMap = new HashMap<>();
		List<ScheduleJob> jobList = new ArrayList<>();
		ScheduleJob scheduleJob;
		TriggerState triggerState;
		String scheduleStatus;
		for (JobExecutionContext executingJob : executingJobs) {
			triggerState = scheduler.getTriggerState(executingJob.getTrigger().getKey());
			scheduleStatus = TriggerState.NORMAL == triggerState ? ScheduleConstant.SH_STATUS_EXECUTING : triggerState.name();
			scheduleJob = jobMap.get(executingJob.getTrigger().getJobKey().getName());
			if(scheduleJob == null) {
				scheduleJob = (ScheduleJob)executingJob.getJobDetail().getJobDataMap().get(ScheduleConstant.JOB_DATA_KEY);
				scheduleJob.setScheduleStatus(scheduleStatus);
				jobMap.put(scheduleJob.getJobKey(), scheduleJob);
				jobList.add(scheduleJob);
			}else {
				scheduleJob.setScheduleStatus(scheduleJob.getScheduleStatus() + "," + scheduleStatus);
			}
		}
		return jobList;
	}
	
	/**
	 * 检查一个任务十分正在执行，此判断只适合单机模式，不适用集群模式
	 * @param scheduleJob
	 * @return
	 * @throws SchedulerException
	 */
	public boolean isRunning(ScheduleJob scheduleJob) throws SchedulerException{
		List<JobExecutionContext> executingJobs = getScheduler().getCurrentlyExecutingJobs();
		for (JobExecutionContext executingJob : executingJobs) {
			JobKey jobKey = executingJob.getJobDetail().getKey();
			if(StringUtils.equalsIgnoreCase(scheduleJob.getJobGroup(), jobKey.getGroup()) 
					&& StringUtils.equalsIgnoreCase(scheduleJob.getJobKey(), jobKey.getName())){
				return true;
			}
		}
		return false;
	}

	/**
	 * 暂停一个job
	 * 
	 * @param scheduleJob
	 * @throws SchedulerException
	 */
	public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
		getScheduler().pauseJob(TaskUtils.getJobKey(scheduleJob));
	}

	/**
	 * 恢复一个job
	 * 
	 * @param scheduleJob
	 * @throws SchedulerException
	 */
	public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException {
		getScheduler().resumeJob(TaskUtils.getJobKey(scheduleJob));
	}

	/**
	 * 删除一个job
	 * 
	 * @param scheduleJob
	 * @throws SchedulerException
	 */
	public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
		getScheduler().deleteJob(TaskUtils.getJobKey(scheduleJob));

	}

	/**
	 * 立即执行job
	 * 
	 * @param scheduleJob
	 * @param param
	 * @throws SchedulerException
	 */
	public void runJobNow(ScheduleJob scheduleJob, Map<String, Object> param) throws SchedulerException {
		
		JobKey jobKey = TaskUtils.getJobKey(scheduleJob);
		if(getScheduler().checkExists(jobKey)){
			if(CollectionUtil.isEmpty(param)){
				getScheduler().triggerJob(jobKey);
			}else{
				JobDataMap jobDataMap = new JobDataMap();
				jobDataMap.put(ScheduleConstant.JOB_PARAM_KEY, param);
				getScheduler().triggerJob(jobKey, jobDataMap);
			}
		}else{
			logger.warn("该定时任务目前没有启用，将直接调用执行该任务的方法, jobKey:{}", jobKey);
			InvokeUtils.invokeJob(scheduleJob, param);
		}
	}
	
	public boolean isExist(ScheduleJob scheduleJob) throws SchedulerException {
		JobKey jobKey = TaskUtils.getJobKey(scheduleJob);
		return getScheduler().checkExists(jobKey);
	}

	/**
	 * 更新job时间表达式
	 * 
	 * @param scheduleJob
	 * @throws SchedulerException
	 */
	public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException {
		Scheduler scheduler = getScheduler();

		TriggerKey triggerKey = TaskUtils.getTriggerKey(scheduleJob);

		CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

		CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionDoNothing();

		trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

		scheduler.rescheduleJob(triggerKey, trigger);
	}


}
